package com.csci;

import java.io.File;
import java.util.Random;

public class IsingSimulation {
	private final double annealing;
	int n;
	int[][] neighbor;
	int numberReject;
	int currentEnergy;
	int proposedEnergy;
	double temperature;
	
	int tempSelf;
	int tempNeighborLeft;
	int tempNeighborRight;
	int tempNeighborTop;
	int tempNeighborBottom;
	
	Random rand;
	Lattice lattice;
	Lattice currentEnergyLattice;
	IsingImage isingImage;
	
	public IsingSimulation(int n, double temperature, double annealing){
		this.n = n;
		lattice = new Lattice(n);
		lattice.initializeLattice();
		currentEnergyLattice = new Lattice(n);
		initializeCurrentLatticeEnergy(lattice, currentEnergyLattice);
		this.neighbor = new int[4][2];
		this.numberReject = 0;
		this.temperature = temperature;
		this.annealing = annealing;
		rand = new Random();
		isingImage = new IsingImage();
	}
		
	public boolean accept(int currentEnergy, int proposedEnergy, double temperature){
		if(proposedEnergy < currentEnergy){
			numberReject = 0;
			return true;
		}
		else if(rand.nextDouble() <= calcAcceptProbability(currentEnergy, proposedEnergy, temperature)){
			numberReject = 0;
			return true;
		}
		else {
			numberReject++;
			return false;
		}
	}
	
	public double calcAcceptProbability(int currentEnergy, int proposedEnergy, double temperature) {
		double absEnergyDifference = (double)Math.abs(proposedEnergy - currentEnergy);
		double invertAbsEnergyDifference = (-1 * absEnergyDifference);
		double  exponentValue = invertAbsEnergyDifference/temperature;
		return Math.pow(Math.E, exponentValue);
	}
	
	public void initializeCurrentLatticeEnergy(Lattice lattice, Lattice currentEnergyLattice ){
		for(int row = 0; row < n; row++){
			for(int col = 0; col < n; col++){
				if(row == col)
					continue;
				currentEnergyLattice.matrix[row][col] = lattice.sumAdjacentLatticeElements(lattice.matrix, row, col);
				currentEnergy -= currentEnergyLattice.matrix[row][col];
			}
		}
	}
	
	public void simulateAnnealing() {
		int intialEnergy = currentEnergy;
		isingImage.generateBitmap(new File("initialState.bmp"),lattice.matrix, n, n);
		System.out.println("Initial State");
		displayEnergyState();	
		while(numberReject < (n*n)){
			int randomRow = rand.nextInt(n);
			int randomCol = rand.nextInt(n);
			setNeighbors(randomRow, randomCol);
			proposedLatticeEnergy(randomRow, randomCol);
			if(accept(currentEnergy, proposedEnergy, temperature)){
				lattice.matrix[randomRow][randomCol] *=-1;
				if(lattice.matrix[randomRow][randomCol] == -1){
					lattice.numberOfNeg++;
					lattice.numberOfPos--;
				}
				else if (lattice.matrix[randomRow][randomCol] == 1 ){
					lattice.numberOfNeg--;
					lattice.numberOfPos++;
				}
				updateCurrentEnergyLattice(randomRow, randomCol);
				currentEnergy = proposedEnergy;
			}
			
			if(lattice.numberOfNeg == (int)((n * n) *.8) || lattice.numberOfPos == (int)((n * n) *.8)){
				isingImage.generateBitmap(new File("80ConvergenceState.bmp"),lattice.matrix, n, n);
				System.out.println("The system at 80% site similarity");
				displayEnergyState();
			}
			
			temperature = decreaseTemperature(temperature);
		}
		isingImage.generateBitmap(new File("finalState.bmp"),lattice.matrix, n, n);
		System.out.println("The final state of the system");
		displayEnergyState();
	}
	
	public void updateCurrentEnergyLattice(int row, int col) {
		currentEnergyLattice.matrix[row][col] = tempSelf;
		currentEnergyLattice.matrix[neighbor[0][0]][neighbor[0][1]] = tempNeighborRight;
		currentEnergyLattice.matrix[neighbor[1][0]][neighbor[1][1]] = tempNeighborLeft;
		currentEnergyLattice.matrix[neighbor[2][0]][neighbor[2][1]] = tempNeighborTop;
		currentEnergyLattice.matrix[neighbor[3][0]][neighbor[3][1]] = tempNeighborBottom;
	}
	
	public void proposedLatticeEnergy(int row, int col){
		  removeLatticeValues(row, col);
		  calcProposedEnergyLattice(row, col);
	}
	
	public void setNeighbors(int row, int col){
		neighbor[0][0] = row;
		neighbor[0][1] = lattice.getRightNeighbor(col);
		
		neighbor[1][0] = row;
		neighbor[1][1] = lattice.getLeftNeighbor(col);
		
		neighbor[2][0] = lattice.getTopNeighbor(row);
		neighbor[2][1] = col;
		
		neighbor[3][0] = lattice.getBottomNeighbor(row);
		neighbor[3][1] = col;
	}
	
	public void removeLatticeValues(int row, int col){
		proposedEnergy = currentEnergy;
		proposedEnergy += currentEnergyLattice.matrix[row][col];
		proposedEnergy += currentEnergyLattice.matrix[neighbor[0][0]][neighbor[0][1]];
		proposedEnergy += currentEnergyLattice.matrix[neighbor[1][0]][neighbor[1][1]];
		proposedEnergy += currentEnergyLattice.matrix[neighbor[2][0]][neighbor[2][1]];
		proposedEnergy += currentEnergyLattice.matrix[neighbor[3][0]][neighbor[3][1]];
	}
	
	public double calcProposedEnergyLattice(int row, int col) {
		lattice.matrix[row][col] *=-1;
		proposedEnergy -= tempSelf = currentEnergyLattice.sumAdjacentLatticeElements(lattice.matrix, row, col);
		proposedEnergy -= tempNeighborRight = currentEnergyLattice.sumAdjacentLatticeElements(lattice.matrix, neighbor[0][0], neighbor[0][1]);
		proposedEnergy -= tempNeighborLeft = currentEnergyLattice.sumAdjacentLatticeElements(lattice.matrix, neighbor[1][0], neighbor[1][1]);
		proposedEnergy -= tempNeighborTop = currentEnergyLattice.sumAdjacentLatticeElements(lattice.matrix, neighbor[2][0], neighbor[2][1]);
		proposedEnergy -= tempNeighborBottom = currentEnergyLattice.sumAdjacentLatticeElements(lattice.matrix, neighbor[3][0], neighbor[3][1]);
		lattice.matrix[row][col] *=-1;
		return proposedEnergy;
	}
	
	public double decreaseTemperature(double temperature){
		return (temperature * annealing);
	}	
	
	public void displayEnergyState() {
		System.out.println("The temperature of the system is: " + temperature);
		System.out.println("The energy of the configuration is " + currentEnergy);
		System.out.println("The number of negative lattice elements is " + lattice.numberOfNeg);
		System.out.println("The number of positive lattice elemetns is " + lattice.numberOfPos);
		if(lattice.numberOfNeg > lattice.numberOfNeg){
			System.out.println( (float)lattice.numberOfNeg/(n*n) +" of the sites are negative ");
		}
		else{
			System.out.println( (float)lattice.numberOfPos/(n*n) +" of the sites are positive ");
		}
		
		System.out.println("\n\n");
	}
}
